package com.shelpe.services.sinbadgateway.controller;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import javax.websocket.server.PathParam;

import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.shelpe.services.sinbadgateway.client.AutoProductQueueClient;
import com.shelpe.services.sinbadgateway.client.CampaignClient;
import com.shelpe.services.sinbadgateway.client.SinbadClient;
import com.shelpe.services.sinbadgateway.entity.jpa.Adgroup;
import com.shelpe.services.sinbadgateway.entity.jpa.AutoProductQueue;
import com.shelpe.services.sinbadgateway.entity.jpa.InprogressAutoDriver;
import com.shelpe.services.sinbadgateway.entity.jpa.OnlineStatus;
import com.shelpe.services.sinbadgateway.repository.jpa.AdgroupRepository;
import com.shelpe.services.sinbadgateway.repository.jpa.AutoProductQueueRepository;
import com.shelpe.services.sinbadgateway.repository.jpa.InprogressAutoDriverRepository;
import com.shelpe.services.sinbadgateway.service.ProductService;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import rx.Observable;
import rx.functions.Action1;

@Controller
@RequestMapping("/sinbadgateway/users/{user_id}")
@Slf4j
public class AutoProductQueueController {

	@Autowired
	private AutoProductQueueRepository autoProductQueueRepository;
	@Autowired
	private AdgroupRepository adgroupRepository;
	@Autowired
	private InprogressAutoDriverRepository inprogressAutoDriverRepository;
	@Autowired
	private ProductService productService;
	@Autowired
	private AutoProductQueueClient autoProductQueueClient;
	@Autowired
	private CampaignClient campaignClient;
	@Autowired
	private SinbadClient sinbadClient;

	@RequestMapping(path = "/autoDrives", method = RequestMethod.POST)
	@ResponseBody
	public AutoDrive createAutoDrive(@PathVariable("user_id") long userID, @RequestBody AutoDrive dto) {
		Assert.isTrue(dto.getCampaignID() > 0, "invalid_campaign_id");
		if (dto.getDriveType() != AutoProductQueue.DriveType.wide.getValue()) {
			Assert.isTrue(dto.getBudget() >= 30 || dto.getBudget() == 0, "budget_less_than_30");
			Assert.isTrue(dto.getMaxPrice() >= 5 || dto.getMaxPrice() == 0, "max_price_less_than_5");
			Assert.notNull(dto.getItems(), "invalid_item");
		} else {
			if (dto.getItems() == null || dto.getItems().size() == 0) {
				this.productService.fillupAutoProduct(userID, dto);
				Assert.notNull(dto.getItems(), "product_not_found");
			}
		}

		List<Adgroup> adgroups = this.adgroupRepository.findByCampaignID(dto.getCampaignID());
		List<AutoProductQueue> queues = new ArrayList<AutoProductQueue>();
		for (AutoDrive.AutoDriveItem item : dto.getItems()) {
			if (!this.exist(adgroups, item.getProductID())) {
				AutoProductQueue queue = new AutoProductQueue();
				BeanUtils.copyProperties(dto, queue);
				BeanUtils.copyProperties(item, queue);
				if (item.getDefaultPrice() <= 0) {
					queue.setDefaultPrice(dto.getDefaultPrice());
				}
				if (item.getMaxPrice() <= 0) {
					queue.setMaxPrice(dto.getMaxPrice());
				}

				queue.setUserID(userID);
				queue.setType(AutoProductQueue.DriveType.from(dto.getDriveType()));
				queue.setCreateTime(new Date());
				queue.setSequence(new Date());
				queue.setLastModifiedTime(new Date());
				queue.setNeedConfirm(false);
				queue.setStatus(AutoProductQueue.Status.added);
				if (queue.getSecondCreativeName() == null) {
					queue.setSecondCreativeName("");
				}
				if (queue.getSecondCreativeUrl() == null) {
					queue.setSecondCreativeUrl("");
				}
				queues.add(queue);
				item.setNeedConfirm(queue.isNeedConfirm());
			}
		}
		this.autoProductQueueRepository.save(queues);

		CampaignClient.CampaignDto campaignDto = new CampaignClient.CampaignDto();
		campaignDto.setBudget(dto.getBudget());
		campaignDto.setUserID(userID);
		campaignDto.setOnlineStatus(OnlineStatus.online);
		log.debug("update campaign budget&&onlineStatus: {}", campaignDto);
		this.campaignClient.updateCampaign(dto.getCampaignID(), campaignDto);

		log.debug("save {} auto drive products", queues.size());
		if (queues.size() > 0) {
			StringBuffer buffer = new StringBuffer();
			int index = 1;
			int chunk = 0;
			int countPerChunk = 0;
			for (AutoProductQueue queue : queues) {
				buffer.append(queue.getProductID()).append("|");
				countPerChunk = countPerChunk + 1;
				if (index % 10 == 0 || index >= queues.size()) {
					InprogressAutoDriver driver = new InprogressAutoDriver();
					driver.setCampaignID(dto.getCampaignID());
					driver.setProductIDs(buffer.toString());
					driver.setStep(1);
					driver.setChunk(chunk);
					driver.setFailure(0);
					driver.setSuccess(0);
					driver.setDone(0);
					driver.setProducts(countPerChunk);
					driver.setCreatedTime(new Date());
					driver.setModifiedTime(new Date());
					this.inprogressAutoDriverRepository.save(driver);

					buffer = new StringBuffer();
					countPerChunk = 0;
					chunk = chunk + 1;
				}
				index = index + 1;
			}
		}

		return dto;
	}

	@RequestMapping(path = "/autoDrivesInitials", method = RequestMethod.GET)
	@ResponseBody
	public AutoDriveInitial initialAutoDrive(@PathVariable("user_id") long userID,
			@RequestParam(name = "campaign_id", required = false, defaultValue = "0") long campaignID) {

		AutoDriveInitial dto = new AutoDriveInitial();

		dto.setCampaignID(campaignID);
		Date day = DateUtils.truncate(new Date(), Calendar.DATE);
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		SinbadClient.MaxPriceDto mdto1 = this.sinbadClient.summariesMetric(userID,
				sdf.format(DateUtils.addDays(new Date(), -7)), sdf.format(day));
		SinbadClient.MaxPriceDto mdto2 = this.sinbadClient.summariesMetric(userID,
				sdf.format(DateUtils.addDays(new Date(), -14)), sdf.format(day));
		if (mdto1 != null && mdto2 != null && mdto1.getCpc() != null && mdto2.getCpc() != null) {
			float temp = (mdto1.getCpc() + mdto2.getCpc()) / 2;
			dto.setMaxPrice((int) (temp * 3.5f > 800.0f ? 800.0f : temp * 3.5f));
		} else {
			dto.setMaxPrice((int) 800.0f);
		}
		return dto;
	}

	@Data
	public static class AutoDriveInitial {
		@JsonProperty("campaign_id")
		private long campaignID;

		@JsonProperty("limit")
		private int maxPrice;

	}

	private boolean exist(List<Adgroup> existingQueues, long productID) {
		boolean exist = false;
		for (Adgroup pq : existingQueues) {

			if (pq.getProductID() == productID) {

				exist = true;
				break;
			}
		}
		return exist;
	}

	@Data
	public static class AutoDrive {
		private boolean status;

		@JsonProperty("campaign_id")
		private long campaignID;

		private int budget;
		@JsonProperty("limit")
		private int maxPrice;
		@JsonProperty("price")
		private int defaultPrice;
		@JsonProperty("drive_type")
		private int driveType;

		private int flag;

		private List<AutoDriveItem> items;

		public void addItem(AutoDriveItem item) {
			if (this.items == null) {
				this.items = new ArrayList<AutoDriveItem>();
			}
			this.items.add(item);
		}

		@Data
		public static class AutoDriveItem {
			@JsonProperty("product_id")
			private long productID;

			@JsonProperty(value = "title1", defaultValue = "")
			private String firstCreativeName;
			@JsonProperty(value = "pic_url1", defaultValue = "")
			private String firstCreativeUrl;
			@JsonProperty(value = "title2", defaultValue = "")
			private String secondCreativeName;
			@JsonProperty(value = "pic_url2", defaultValue = "")
			private String secondCreativeUrl;

			@JsonProperty("limit")
			private int maxPrice;
			@JsonProperty("price")
			private int defaultPrice;

			@JsonProperty("need_confirm")
			private boolean needConfirm;
		}
	}

	@RequestMapping(path = "/campaigns/{campaign_id}/autoDrives/@start", method = RequestMethod.GET)
	@ResponseBody
	public DeferredResult<AutoDriveStarter> startAutoDrive(@PathVariable("campaign_id") long campaignID) {
		final DeferredResult<AutoDriveStarter> result = new DeferredResult<AutoDriveStarter>();

		final AutoDriveStarter dto = new AutoDriveStarter();

		List<AutoProductQueue> productQueues = new ArrayList<AutoProductQueue>();
		List<AutoProductQueue> existingQueues = this.autoProductQueueRepository.findByCampaignID(campaignID);
		for (AutoProductQueue pq : existingQueues) {
			if (pq.getStatus() == AutoProductQueue.Status.added) {
				productQueues.add(pq);
			} else {
				log.warn("the product {} has already driven: {}", pq.getProductID(), pq);
			}
		}

		if (productQueues.size() == 0) {
			log.error("not found AutoProductQueue for {}", campaignID);
			throw new IllegalArgumentException("auto_drive_queue_not_found");
		}

		for (final AutoProductQueue queue : productQueues) {
			log.debug("auto drive for product {} in campaign {}", queue.getProductID(), queue.getCampaignID());

			AutoDriveStarter.AutoDriveItem item = new AutoDriveStarter.AutoDriveItem();
			item.setProductID(queue.getProductID());
			item.setStatus("running");
			dto.addItem(item);

			Observable<AutoProductQueueClient.AutoDrivingDto> observer = this.autoProductQueueClient
					.startAutoDrive(queue.getCampaignID(), queue.getProductID());

			observer.subscribe(new Action1<AutoProductQueueClient.AutoDrivingDto>() {
				@Override
				public void call(AutoProductQueueClient.AutoDrivingDto drivingRes) {
					if (log.isDebugEnabled()) {
						log.debug("the result to start drive: {}", drivingRes);
					}
					AutoDriveStarter.AutoDriveItem found = filterItem(dto.getItems(), drivingRes.getProductID());
					if (found != null) {
						if (drivingRes.isSuccess()) {
							found.setStatus("completed");
						} else {
							found.setStatus("fail");
						}
					}
					boolean allDone = true;
					for (AutoDriveStarter.AutoDriveItem item : dto.getItems()) {
						allDone = allDone && !"running".equals(item.getStatus());
					}

					if (allDone) {
						dto.setSuccess(true);
						result.setResult(dto);
					}
				}

			});
		}
		dto.setCampaignID(campaignID);

		return result;
	}

	private AutoDriveStarter.AutoDriveItem filterItem(List<AutoDriveStarter.AutoDriveItem> items, long productID) {
		for (AutoDriveStarter.AutoDriveItem item : items) {
			if (item.getProductID() == productID) {
				return item;
			}
		}
		return null;
	}

	@Data
	public static class AutoDriveStarter {
		private boolean success;

		@JsonProperty("campaign_id")
		private long campaignID;
		private List<AutoDriveItem> items;

		public void addItem(AutoDriveItem item) {
			if (this.items == null) {
				this.items = new ArrayList<AutoDriveItem>();
			}
			this.items.add(item);
		}

		@Data
		public static class AutoDriveItem {
			@JsonProperty("product_id")
			private long productID;
			@JsonProperty("adgroup_id")
			private long adgroupID;

			private String status = "waiting";
		}

	}
}
